package com.millstein.service.impl;

import cn.hutool.core.util.StrUtil;
import com.millstein.constant.RedisKey;
import com.millstein.enums.BizCodeEnum;
import com.millstein.enums.user.SendCodeEnum;
import com.millstein.exception.BizException;
import com.millstein.service.MailService;
import com.millstein.service.NotifyService;
import com.millstein.util.CheckUtils;
import com.millstein.util.CommonUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: tsing
 * @create: 2021-09-11 20:58
 */
@Service
@Slf4j
public class NotifyServiceImpl implements NotifyService {

    @Autowired
    private MailService mailService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 验证码标题
     */
    private static final String SUBJECT = "小滴课堂验证码";

    /**
     * 验证码内容
     */
    private static final String CONTENT = "你的验证码是%s，有效时间是60秒，不要告诉任何人";

    /**
     * 发送邮箱验证码。如何解决重复发送有三种方法：
     *  1.前端校验。这种方法缺陷比较大，如果对方知道了你的接口，那么这张方法根本没有作用
     *  2.使用redis，发送时记录验证码和已经发送的标记（并且设置超时时间）。这个方法是常用方法，问题在于向redis中存验证码和已经发送的标记是
     *      两步操作，不是原子操作，所以可能会存在不一致的问题
     *  3.向redis中存储一次数据，这次数据需要把验证码和当前时间戳拼接在一起，这样既实现了功能，也使得这个过程中只有一次redis的存储
     *
     * @param opt 操作类型
     * @param to  接收人
     */
    @Override
    public void sendCode(SendCodeEnum opt, String to) {
        // 1.获取redis中存储的信息，包括验证码信息和时间戳
        String redisKey = String.format(RedisKey.CHECK_CODE_KEY, opt.name(), to);
        String redisValue = redisTemplate.opsForValue().get(redisKey);

        // 如果不为空则判断60s内是否重复发送过
        if (StrUtil.isNotBlank(redisValue)) {
            long ttl = Long.parseLong(redisValue.split("_")[1]);
            // 当前时间-ttl < 60，那么就认为它重复发送了
            long interval = CommonUtils.getCurrentTimestamp();
            if (interval < 60 * 1000) {
                log.info("重复发送验证码，时间间隔为{}秒", interval / 1000);
                throw new BizException(BizCodeEnum.CODE_LIMITED);
            }
        }

        // 获取验证码
        String code = CommonUtils.getRandomCode(6);
        String content = String.format(CONTENT, code);

        // 拼接存在redis中的内容，格式为（验证码_时间戳）
        String value = code + "_" + CommonUtils.getCurrentTimestamp();

        // 将内容存到redis中去（10分钟有效）
        redisTemplate.opsForValue().set(redisKey, value, 60 * 10, TimeUnit.SECONDS);

        // 根据收件人的不同进行不同方式的发送
        if (CheckUtils.isEmail(to)) {
            // 邮箱
            mailService.sendMail(to, SUBJECT, content);
            return;
        } else if (CheckUtils.isPhone(to)) {
            // TODO 手机号码
        }
        throw new BizException(BizCodeEnum.CODE_TO_ERROR);
    }

    /**
     * 校验验证码
     *
     * @param opt  操作类型
     * @param to   接收人
     * @param code 待校验的验证码
     * @return
     */
    @Override
    public boolean checkCode(SendCodeEnum opt, String to, String code) {
        if (StrUtil.isBlank(to) || StrUtil.isBlank(code)) {
            return false;
        }

        // 1.获取redis中存储的信息，包括验证码信息和时间戳
        String redisKey = String.format(RedisKey.CHECK_CODE_KEY, opt.name(), to);
        String redisValue = redisTemplate.opsForValue().get(redisKey);

        // 如果不为空则判断60s内是否重复发送过
        if (StrUtil.isBlank(redisValue)) {
            return false;
        }

        String cachedCode = redisValue.split("_")[0];
        if (cachedCode.equals(code)) {
            // 成功后需要删除key
            redisTemplate.delete(redisKey);
            return true;
        }
        return false;
    }
}
